#include "CECascadeSystem.h"

using namespace SP;

//-------------------------------------------------------------
// ECState
//-------------------------------------------------------------
class CEState
{
public:
	CEState();
	double			m_fAbsoluteTime;
	double			m_fTime;
	float				m_fElapsedTime;
	float				m_fFPS;
	float				m_fConstantTimePerFrame;
	double			m_fLastStateUpdateTime;
	DWORD		m_dwLashtStateUpdateFrames;
	WCHAR		m_wcsFPSStats[64];
};

CEState::CEState()
{
	m_fAbsoluteTime = 0;
	m_fTime = 0;
	m_fElapsedTime = 0;
	m_fFPS = 0;
	m_fConstantTimePerFrame = 0;
	m_fLastStateUpdateTime = 0;
	m_dwLashtStateUpdateFrames = 0;
}

CEState* CEGetState()
{
	static CEState staticState;
	return &staticState;
}

SPTimer* CEGetGlobalTimer()
{
	static SPTimer timer;
	return &timer;
}

void CEUpdateFrameStats()
{
	// Keep track of the frame count
	double fLastTime = CEGetState()->m_fLastStateUpdateTime;
	DWORD dwFrames = CEGetState()->m_dwLashtStateUpdateFrames;
	double fAbsTime = CEGetState()->m_fAbsoluteTime;
	dwFrames++;
	CEGetState()->m_dwLashtStateUpdateFrames = dwFrames;

	// Update the scene stats once per second
	if ( fAbsTime - fLastTime > 1.0f )
	{
		float fFPS = ( float )( dwFrames / ( fAbsTime - fLastTime ) );
		CEGetState()->m_fFPS = fFPS;
		CEGetState()->m_fAbsoluteTime = fAbsTime;
		CEGetState()->m_fLastStateUpdateTime = 0;

		swprintf_s( CEGetState()->m_wcsFPSStats, 64, L"%0.2f fps ", fFPS );
	}
}

//-------------------------------------------------------------
// ECCascadeSystem
//-------------------------------------------------------------
CECascadeSystem* CEGetCascadeSystem()
{
	static CECascadeSystem staticCECS;
	return &staticCECS;
}

void CECascadeSystem::Create( HWND hWnd, UINT width, UINT height )
{		
	m_pCamera = new SPModelViewerCamera();
	m_pCamera->SetWindow( width, height );
	m_pCamera->OnBegin( 0, 0 );
	m_pCamera->OnMove( 1, 1 );
	m_pCamera->OnEnd();

	SPD3D11RenderEnvironment* pRenderEnvironment;
	pRenderEnvironment = SPGetRenderEnvironment();
	pRenderEnvironment->Create( hWnd, width, height );
	pRenderEnvironment->SetCamera( m_pCamera );
}

void CECascadeSystem::SetWindow( UINT width, UINT height )
{
	m_pCamera->SetWindow( width, height );
	SPGetRenderEnvironment()->SetWindow( width, height );
}

CECascadeSystem::CECascadeSystem()
{
	m_pCamera = NULL;
	m_bPause = false;
	m_bDrawCoordinate = true;
}

CECascadeSystem::~CECascadeSystem()
{
	delete m_pCamera;
}

void CECascadeSystem::OnFrame()
{
	// UpdateFrameStats
	double fTime, fAbsTime; float fElapsedTime;
	CEGetGlobalTimer()->GetTimeValues( &fTime, &fAbsTime, &fElapsedTime );

	if ( CEGetState()->m_fConstantTimePerFrame )
	{
		fElapsedTime = CEGetState()->m_fConstantTimePerFrame;
		fTime = CEGetState()->m_fTime + fElapsedTime;
	}

	CEGetState()->m_fTime = fTime;
	CEGetState()->m_fElapsedTime = fElapsedTime;
	CEGetState()->m_fAbsoluteTime = fAbsTime;

	CEUpdateFrameStats();

	// Move all components 
	if ( !m_bPause )
	{
		for ( SPEmitterList::iterator it = m_EmitterList.begin(); it != m_EmitterList.end(); it++ )
		{
			( *it )->Advance( fElapsedTime );
		}
	}

	// Clear
	SPGetRenderEnvironment()->Clear( m_vBGColor );
	
	// Render all components
	if ( m_bDrawCoordinate )
	{
		DrawCoordinate();
	}

	for ( SPEmitterList::iterator it = m_EmitterList.begin(); it != m_EmitterList.end(); it++ )
	{
		if ( !( *it )->IsDead() && ( *it )->IsDepthEnable() )
			( *it )->Render();
	}

	for ( SPEmitterList::iterator it = m_EmitterList.begin(); it != m_EmitterList.end(); it++ )
	{
		if (  !( *it )->IsDead() && !( *it )->IsDepthEnable() )
			( *it )->Render();
	}

	// Present
	SPGetRenderEnvironment()->Present();
}

void CECascadeSystem::DrawCoordinate()
{		
	struct CB
	{
		XMMATRIX mWorldViewProj;
	};

	static ID3D11VertexShader* g_pVertexShader = NULL;
	static ID3D11PixelShader*	g_pPixelShader = NULL;
	static ID3D11Buffer* g_pCB = NULL;

	HRESULT hr;

	if ( g_pVertexShader == NULL )
	{
		// Create shaders
		ID3DBlob* pVSBlob = NULL;
		ID3DBlob* pPSBlob = NULL;

		const char* g_strBuffer =
		"	cbuffer cbPerObject : register( b0 )																\r\n"
		"	{																													\r\n"
		"		matrix      g_mWorldViewProjection  : packoffset( c0 );								\r\n"
		"	};																													\r\n"
		"																														\r\n"
		"	struct VS_In																									\r\n"
		"	{																													\r\n"
		"		uint    id      	: SV_VERTEXID;																		\r\n"
		"	};																													\r\n"
		"																														\r\n"
		"	struct VS_Out																								\r\n"
		"	{																													\r\n"
		"		float4 pos			: SV_POSITION;																	\r\n"
		"		float4 color        : COLOR;																			\r\n"
		"	};																													\r\n"
		"																														\r\n"
		"																														\r\n"
		"	cbuffer cbImmutable																					\r\n"
		"	{																													\r\n"
		"		static float4 g_positions[6] =																		\r\n"
		"		{																												\r\n"
		"			float4( 0.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 1.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 1.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 0.0f, 1.0f, 1.0f ),        															\r\n"
		"		};																												\r\n"
		"																														\r\n"
		"		static float4 g_colors[6] =																			\r\n"
		"		{																												\r\n"
		"			float4( 1.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 1.0f, 0.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 1.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 1.0f, 0.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 0.0f, 1.0f, 1.0f ),																	\r\n"
		"			float4( 0.0f, 0.0f, 1.0f, 1.0f ),        															\r\n"
		"		};																												\r\n"
		"	};																													\r\n"
		"	VS_Out VSMain(VS_In input)																			\r\n"
		"	{																													\r\n"
		"		VS_Out output;																							\r\n"
		"																														\r\n"
		"		output.pos = mul( g_positions[input.id] * 5, g_mWorldViewProjection );		\r\n"
		"		output.color = g_colors[input.id];																\r\n"
		"		return output;																							\r\n"
		"	}																													\r\n"
		"																														\r\n"
		"	float4 PSMain(VS_Out input) : SV_Target														\r\n"
		"	{   																												\r\n"
		"		return input.color;																						\r\n"
		"		return float4( 1.0f, 1.0f, 0.0f, 1.0f );																\r\n"
		"	}																													\r\n"
		"";
		UINT dwBufferSize = ( UINT )strlen( g_strBuffer ) + 1;
		V( SPGetRenderEnvironment()->CompileShaderFromMemory( g_strBuffer, dwBufferSize, NULL, "VSMain", "vs_5_0", &pVSBlob ) );
		V( SPGetRenderEnvironment()->CompileShaderFromMemory( g_strBuffer, dwBufferSize, NULL, "PSMain", "ps_5_0", &pPSBlob ) );

		V( SPGetD3D11Device()->CreateVertexShader( pVSBlob->GetBufferPointer(), pVSBlob->GetBufferSize(), NULL, &g_pVertexShader ) );
		V( SPGetD3D11Device()->CreatePixelShader( pPSBlob->GetBufferPointer(), pPSBlob->GetBufferSize(), NULL, &g_pPixelShader ) );

		SAFE_RELEASE( pVSBlob );
		SAFE_RELEASE( pPSBlob );

		// Create the constant buffers
		D3D11_BUFFER_DESC Desc;
		Desc.Usage = D3D11_USAGE_DYNAMIC;
		Desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		Desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
		Desc.MiscFlags = 0;
		Desc.ByteWidth = sizeof( CB );
		V( SPGetD3D11Device()->CreateBuffer( &Desc, NULL, &g_pCB ) );
	}

	ID3D11DeviceContext* pImmediateContext = SPGetD3D11DeviceContext();
	D3D11_MAPPED_SUBRESOURCE MappedResource;
	V(pImmediateContext->Map( g_pCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );
	CB* pPerFrame = ( CB* )MappedResource.pData;
	pPerFrame->mWorldViewProj = XMMatrixTranspose( SPGetCamera()->GetViewProjMtx() );
	pImmediateContext->Unmap( g_pCB, 0 );
	pImmediateContext->VSSetConstantBuffers( 0, 1, &g_pCB );

	pImmediateContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_LINELIST );

	pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );
	pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );
	pImmediateContext->GSSetShader( NULL, NULL, 0 );

	// Draw
	pImmediateContext->Draw( 6, 0 );
}

void CECascadeSystem::AddEmitter( SP::SPEmitter* pEmitter )
{
	m_EmitterList.push_back( pEmitter );
}

void CECascadeSystem::RemoveEmitter( SP::SPEmitter* pEmitter )
{
	m_EmitterList.remove( pEmitter );
}

void CECascadeSystem::InitEmitter()
{
	for ( SPEmitterList::iterator it = m_EmitterList.begin(); it != m_EmitterList.end(); it++ )
	{
		( *it )->Initialize();
	}
}

void CECascadeSystem::Import( const WCHAR* path )
{
	enum
	{
		EMITTER_NONE,
		EMITTER_MESH,
		EMITTER_PARTICLE,
		EMITTER_ANIMTRAIL,
		EMITTER_BEAM
	};

	HANDLE hFile = CreateFile( path, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,
		NULL );
    if( INVALID_HANDLE_VALUE == hFile )
		return;

	LARGE_INTEGER FileSize;
	GetFileSizeEx( hFile, &FileSize );
	UINT cBytes = FileSize.LowPart;

	BYTE* pBufPtr = new BYTE[cBytes];

	DWORD dwBytesRead;
	if( !ReadFile( hFile, pBufPtr, cBytes, &dwBytesRead, NULL ) )
		return;

	// close file
	CloseHandle( hFile );
	pBufPtr += sizeof( UINT );


	// compile
	BYTE* pBuf = pBufPtr;
	UINT* pNumEmitters = ( UINT* ) pBuf;
	pBuf += sizeof( UINT );
	// 
	for ( int i = 0; i < *pNumEmitters; i++ )
	{
		// Type
		UINT* pEmitterType = ( UINT* )pBuf;
		pBuf += sizeof( UINT );
		// EmitterBufferSize
		UINT* pEmitterBufferSize = ( UINT* )pBuf;
		pBuf += sizeof( UINT );

		SPEmitter* pNewEmitter = 0;
		switch( *pEmitterType )
		{
		case EMITTER_MESH:
			pNewEmitter = new SPEmitterMesh();
			break;
		case EMITTER_PARTICLE:
			pNewEmitter = new SPEmitterParticle();
			break;
		case EMITTER_BEAM:
			pNewEmitter = new SPEmitterBeam();
			break;
		case EMITTER_ANIMTRAIL:
			pNewEmitter = new SPEmitterAnimTrail();
			break;
		}

		if ( pNewEmitter )
		{
			pNewEmitter->CreateFromFileBuffer( pBuf );
			m_EmitterList.push_back( pNewEmitter );
		}

		pBuf += *pEmitterBufferSize;
	}	

}